package br.com.wepa.webapps.orca.logica.persistencia.hibernate;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.PersistenceException;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;

import br.com.wepa.webapps.logger.TraceLogger;
import br.com.wepa.webapps.orca.logica.negocio.BusinessConstants;
import br.com.wepa.webapps.orca.logica.persistencia.DynaStatement;
import br.com.wepa.webapps.orca.logica.persistencia.Persistence;
import br.com.wepa.webapps.orca.logica.persistencia.search.BeanSearch;
import br.com.wepa.webapps.orca.logica.persistencia.search.NamedQuerySearch;
import br.com.wepa.webapps.orca.logica.persistencia.search.QuerySearch;
import br.com.wepa.webapps.search.PagingBean;
import br.com.wepa.webapps.search.SearchResult;

public class PersistenceHibernate  extends HibernateHelper implements Persistence,BusinessConstants{	
	
	private static PersistenceHibernate single;

	public static PersistenceHibernate getInstance() {
		if (single == null) {
			single = new PersistenceHibernate();
		}
		return single;
	}

	private PersistenceHibernate() {
		init();
	};

	/**
	 * ControleWeb de log
	 */
	@SuppressWarnings("unused")
	private TraceLogger logger = new TraceLogger(PersistenceHibernate.class);

	@SuppressWarnings("unchecked")
	public SearchResult searchNamedQuery(NamedQuerySearch query)
			throws PersistenceException {
		SearchResult searchResult = query.getSearchResult();;
		try {
			Session session = getCurrentSession();
			Query hQuery = session.getNamedQuery(query.getQueryName());
			hQuery.setReadOnly(query.isReadOnly());
			for (int i = 0; i < query.getParams().length; i++) {
				hQuery = hQuery.setParameter(i, query.getParam(i));
			}

			if(query.isPagingEnabled()){
				doPaging(hQuery.scroll(), searchResult, query.getPagingBean());
			}else{
				searchResult.setResult(hQuery.list());
			}
			
			return searchResult;
		} catch (HibernateException he) {
			throw HibernateHelper.trataException(he);
		}
	}


	@SuppressWarnings("unchecked")
	private void doPaging(ScrollableResults scrollableResult, SearchResult searchResult, PagingBean paging ) {	
		ArrayList result = new ArrayList();
		paging.setPageSize(paging.getPageSize() != 0 ? paging.getPageSize()
							: BusinessConstants.DEFAULT_RESULTS_PER_PAGE);
		if (scrollableResult.setRowNumber(paging.getFirstRowInPage()) ) {					
			int i = 0;
		    do{
		    	Object[] array = scrollableResult.get();
		    	if(array.length == 1) result.add(array[0]);
		    	else result.add(array);
		    	i += 1;
		    }while( ( paging.getPageSize() > i ) && scrollableResult.next() );
		}				
		scrollableResult.last();
		paging.setCountRows(scrollableResult.getRowNumber() + 1);	
		scrollableResult.close();
		searchResult.setResult(result);
		searchResult.setPagingBean(paging);
	}
	
	
	@SuppressWarnings("unchecked")
	public SearchResult searchQuery(QuerySearch query) throws PersistenceException {	
		SearchResult searchResult = query.getSearchResult();		
		try {
			Session session = getCurrentSession();
			Query hQuery = null;
			
			if(query.isSql()) hQuery = session.createSQLQuery(query.mount());
			else if(query.isNonSql()) hQuery = session.createQuery(query.mount());

			hQuery.setReadOnly(query.isReadOnly());
			
			for(int i = 0; i < query.getParamsList().size(); i++) {
				hQuery = hQuery.setParameter(i, query.getParam(i));
			}
						
			if(query.isPagingEnabled()){
				doPaging(hQuery.scroll(), searchResult, query.getPagingBean());
			}else{
				searchResult.setResult(hQuery.list());
			}
			
			return searchResult;
		} catch (HibernateException he) {
			throw HibernateHelper.trataException(he);
		}
	}
	
	@SuppressWarnings("unchecked")
	public SearchResult searchBean(BeanSearch search)
			throws PersistenceException {
		SearchResult searchResult = search.getSearchResult();
		try {
			Criteria crit = getCurrentSession().createCriteria(search.getBeanInstance().getClass());
			
			Example example = search.getExample();				
			crit.add(example);			
			for (int i = 0; i < search.getOrderPropertyList().size(); i++) {
				if (search.getOrder(i).isAscending()) {
					crit.addOrder(Order.asc(search.getOrder(i).getProperty()));
				} else {
					crit.addOrder(Order.desc(search.getOrder(i).getProperty()));
				}
			}

			
			crit.setFetchSize(HIBERNATE_MAX_FETCH_SIZE);
			if(search.isPagingEnabled()){
				doPaging(crit.scroll(), searchResult, search.getPagingBean());
			}else{
				searchResult.setResult(crit.list());
			}		
			
			return searchResult;
		} catch (HibernateException he) {
			throw HibernateHelper.trataException(he);
		}
	}


	public Object uniqueResultNamedQuery(String queryName, Object... params)
			throws PersistenceException {
		try {
			Session session = getCurrentSession();
			Query pQuery = session.getNamedQuery(queryName);

			if(params !=null){
				for (int i = 0; i < params.length; i++) {
					pQuery = pQuery.setParameter(i, params[i]);
				}
			}
			return pQuery.uniqueResult();
		} catch (HibernateException he) {
			throw HibernateHelper.trataException(he);
		}
	}

	public void executeUpdate(DynaStatement query)
			throws PersistenceException {
		try {
			Session session = getCurrentSession();
			
			Query pQuery = null;
			if(query.isSql()) pQuery = session.createSQLQuery(query.mount());
			else if(query.isNonSql()) pQuery = session.createQuery(query.mount());
			
			for (int i = 0; i < query.getParamsList().size(); i++) {
				pQuery = pQuery.setParameter(i, query.getParam(i));
			}
			
			pQuery.executeUpdate();
		} catch (HibernateException he) {
			throw HibernateHelper.trataException(he);
		}
	}

	/**
	 * 
	 * @param c
	 *            Class da pesquisa
	 * @param criterions
	 *            Criterios usado na pesquisa
	 * @param orders
	 * @param projection
	 * @return
	 * @throws PersistenceException
	 */
	public List list(Class c) throws PersistenceException {
		return list(c, null, null, null);
	}

	/**
	 * 
	 * @param c
	 *            Class da pesquisa
	 * @param criterions
	 *            Criterios usado na pesquisa
	 * @param orders
	 * @param projection
	 * @return
	 * @throws PersistenceException
	 */
	public List list(Class c, Criterion[] criterions, Order[] orders,
			Projection projection) throws PersistenceException {
		try {
			Session session = getCurrentSession();

			Criteria criteria = session.createCriteria(c);

			if(criterions !=null)
				for (Criterion criterion : criterions) {
					criteria = criteria.add(criterion);
				};

			if(orders !=null)
				for (Order order : orders) {
					criteria = criteria.addOrder(order);
				};

			if (projection != null)
				criteria = criteria.setProjection(projection);
			

			return criteria.list();
		} catch (HibernateException he) {
			
			throw HibernateHelper.trataException(he);
		}
	}

	/**
	 * 
	 * @param id
	 * @param lock
	 * @return the entity, must return null if not found
	 */
	@SuppressWarnings("unchecked")
	public Object findById(Class clazz, Serializable id, boolean lock) {
		Object entity;

		if (lock)
			entity = getCurrentSession().get(clazz, id, LockMode.UPGRADE);
		else
			entity = getCurrentSession().get(clazz, id);
		
		return entity;
	}

	@SuppressWarnings("unchecked")
	public List findAll(Class clazz) {
		return findByCriteria(clazz);
	}


	public List findByExample(Object exampleInstance, String... excludeProperty) {	
		Criteria crit = getCurrentSession().createCriteria(
				exampleInstance.getClass());		
		Example example = Example.create(exampleInstance);	
		example.excludeNone();
		example.excludeZeroes();
		if(excludeProperty !=null){
			for (String exclude : excludeProperty) {
				example.excludeProperty(exclude);
			}
		}
		crit.add(example);
		crit.setFetchSize(3);
		return crit.list();
	}
	


	public Object findUnique(Object exampleInstance, String... excludeProperty) {		
		Criteria crit = getCurrentSession().createCriteria(
				exampleInstance.getClass());
		Example example = Example.create(exampleInstance);
		example.excludeNone();
		example.excludeZeroes();
		if(excludeProperty !=null){
			for (String exclude : excludeProperty) {
				example.excludeProperty(exclude);
			}
		}
		crit.add(example);
		return crit.uniqueResult();
	}
	
	public Serializable create(Object entity) {
		return getCurrentSession().save(entity);		
	}

	public boolean exists(Class clazz, Serializable id) {
		return findById(clazz, id, false) != null;
	}

	public void refresh(Object entity) {
		getCurrentSession().refresh(entity);
	}

	public Object makePersistent(Object entity) {
		return getCurrentSession().merge(entity);
	}

	public void makeTransient(Object entity) {
		getCurrentSession().delete(entity);
	}

	

	/**
	 * Use this inside subclasses as a convenience method.
	 */
	protected List findByCriteria(Class clazz) {
		return findByCriteria(clazz, (Criterion)null);
	}

	/**
	 * Use this inside subclasses as a convenience method.
	 */
	protected List findByCriteria(Class clazz, Criterion... criterion) {		
		Criteria crit = getCurrentSession().createCriteria(clazz);
		if(criterion !=null ){
			for (Criterion c : criterion) {
				crit.add(c);
			}
		}
		return crit.list();
	}	
		
 
	protected void init() {
		// could connect or connect lazy
		// connect();
	}

	
	
}
